#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>

#include "imbus_internal.h"
#include "im_memory_chunk.h"
#include "im_stream.h"
#include "im_byte_stream.h"

#define BYTE_STREAM_MAGIC               0x45545942
#define BYTE_STREAM_PROTOCOL_VERSION    20070705

#define MAX_ALLOC_BLOCK_SIZE            65536

/**
 * Format of Byte stream:
 * 4 bytes: magic.
 * 4 bytes: protocol version.
 * 4 bytes: size.
 * 4 bytes: checksum.
 * N bytes: data.
 *
 * The format of data:
 * 1. For fundamental single element type, such as INT16, DOUBLE, etc:
 * 2 bytes: type id (converted to bytes sequence, Little endian)
 * M bytes: data (converted to bytes sequence, Little endian),
 *          where M = im_type_get_instance_size(type).
 *
 * 2. For fundamental array type, suzhe as INT16_ARRAY,etc:
 * 2 bytes: type id (converted to bytes sequence, Little endian)
 * 4 bytes: number of elements (converted to bytes sequence, Little endian).
 * M*N bytes: data elements (converted to bytes sequences, Little endian).
 *            where M = im_type_get_instance_size(type), N = number of elements.
 *
 * 3. For an built-in object type:
 * 2 bytes: type id (converted to bytes sequence, Little endian)
 * N bytes: object data.
 *
 * 4. For an user-defined object type:
 * 2 bytes: type id (converted to bytes sequence, Little endian)
 * 1 byte: length of type name (L)
 * L bytes: type name string
 * N bytes: object data.
 */

struct _IMByteStream
{
    IMStream parent_object;

    IMMemoryChunk   *_chunk;

    IMSize           _write_pos;
    IMSize           _read_pos;

    IMType           _cur_type;
    IMSize           _cur_size;
    IMSize           _cur_type_len;
    IMSize           _cur_size_len;
};

struct _IMByteStreamClass
{
    IMStreamClass    parent_class;
};

static const IMObjectClass *__im_byte_stream_parent_class = 0;

#define UINT64_TO_BYTES(bytes,val)                                          \
    do {                                                                    \
        ((IMUChar*)(bytes))[0] = (IMUChar)((((IMUInt64)val) & 0xFF));       \
        ((IMUChar*)(bytes))[1] = (IMUChar)((((IMUInt64)val) >> 8)  & 0xFF); \
        ((IMUChar*)(bytes))[2] = (IMUChar)((((IMUInt64)val) >> 16) & 0xFF); \
        ((IMUChar*)(bytes))[3] = (IMUChar)((((IMUInt64)val) >> 24) & 0xFF); \
        ((IMUChar*)(bytes))[4] = (IMUChar)((((IMUInt64)val) >> 32) & 0xFF); \
        ((IMUChar*)(bytes))[5] = (IMUChar)((((IMUInt64)val) >> 40) & 0xFF); \
        ((IMUChar*)(bytes))[6] = (IMUChar)((((IMUInt64)val) >> 48) & 0xFF); \
        ((IMUChar*)(bytes))[7] = (IMUChar)((((IMUInt64)val) >> 56) & 0xFF); \
    } while(0)

#define BYTES_TO_UINT64(bytes)          \
    ((((IMUInt64)(bytes)[0]))      |    \
     (((IMUInt64)(bytes)[1]) << 8) |    \
     (((IMUInt64)(bytes)[2]) << 16)|    \
     (((IMUInt64)(bytes)[3]) << 24)|    \
     (((IMUInt64)(bytes)[4]) << 32)|    \
     (((IMUInt64)(bytes)[5]) << 40)|    \
     (((IMUInt64)(bytes)[6]) << 48)|    \
     (((IMUInt64)(bytes)[7]) << 56))

#define UINT32_TO_BYTES(bytes,val)                                          \
    do {                                                                    \
        ((IMUChar*)(bytes))[0] = (IMUChar)((((IMUInt32)val) & 0xFF));       \
        ((IMUChar*)(bytes))[1] = (IMUChar)((((IMUInt32)val) >> 8)  & 0xFF); \
        ((IMUChar*)(bytes))[2] = (IMUChar)((((IMUInt32)val) >> 16) & 0xFF); \
        ((IMUChar*)(bytes))[3] = (IMUChar)((((IMUInt32)val) >> 24) & 0xFF); \
    } while(0)

#define BYTES_TO_UINT32(bytes)                      \
    (((IMUInt32)(((IMUChar*)(bytes))[0]))      |    \
     ((IMUInt32)(((IMUChar*)(bytes))[1]) << 8) |    \
     ((IMUInt32)(((IMUChar*)(bytes))[2]) << 16)|    \
     ((IMUInt32)(((IMUChar*)(bytes))[3]) << 24))

#define UINT16_TO_BYTES(bytes,val)                                          \
    do {                                                                    \
        ((IMUChar*)(bytes))[0] = (IMUChar)((((IMUInt16)val) & 0xFF));       \
        ((IMUChar*)(bytes))[1] = (IMUChar)((((IMUInt16)val) >> 8)  & 0xFF); \
    } while(0)

#define BYTES_TO_UINT16(bytes)                  \
    (((IMUInt16)(((IMUChar*)(bytes))[0])) |     \
     ((IMUInt16)(((IMUChar*)(bytes))[1]) << 8))

#define UINT8_TO_BYTES(bytes,val)                           \
    do {                                                    \
        ((IMUChar*)(bytes))[0] = (IMUChar)((IMUInt8)val);   \
    } while(0)

#define BYTES_TO_UINT8(bytes)   ((IMUInt8)(bytes[0]))

static void
__free_unused_space (const IMByteStream *stream)
{
    if (!stream->_write_pos)
        im_memory_chunk_clear (stream->_chunk);
    else if (im_memory_chunk_get_size (stream->_chunk) > stream->_write_pos)
        im_memory_chunk_reduce_size (stream->_chunk, stream->_write_pos);
}

static inline IMBool
__ensure_free_space (IMByteStream *stream, IMSize free_size)
{
    IMSize old_size = im_memory_chunk_get_size (stream->_chunk);
    if ( old_size < stream->_write_pos + free_size) {
        IMSize new_size = stream->_write_pos + free_size;

        if (old_size < MAX_ALLOC_BLOCK_SIZE && new_size - old_size < old_size * 2)
            new_size = old_size * 2;
        else if (new_size - old_size < MAX_ALLOC_BLOCK_SIZE)
            new_size = old_size + MAX_ALLOC_BLOCK_SIZE;

        return im_memory_chunk_ensure_size (stream->_chunk, new_size);
    }
    return TRUE;
}

static inline void
__clear_cur_info (IMByteStream *stream)
{
    stream->_cur_type       = IM_TYPE_INVALID;
    stream->_cur_size       = 0;
    stream->_cur_type_len   = 0;
    stream->_cur_size_len   = 0;
}

#define ENSURE_FREE_SPACE(bsp,size)                 \
    do {                                            \
        if (!__ensure_free_space ((bsp), (size)))   \
            return FALSE;                           \
    } while (0)

static IMBool
__im_byte_stream_put_void (IMStream *stream)
{
    IMByteStream *tsp = IM_BYTE_STREAM (stream);
    IMUChar *ptr;
    _im_assert (tsp);
    ENSURE_FREE_SPACE (tsp, 2);
    ptr = (IMUChar *) im_memory_chunk_get_ptr (tsp->_chunk);
    ptr += tsp->_write_pos;
    UINT16_TO_BYTES(ptr, IM_TYPE_VOID);
    tsp->_write_pos += 2;
    return TRUE;
}

#define PUT_FUNDAMENTAL_VALUE_TYPE(stream,type,width,value)         \
    do  {                                                           \
        IMByteStream *tsp = IM_BYTE_STREAM (stream);                \
        IMUChar      *ptr;                                          \
        _im_assert (tsp);                                           \
        ENSURE_FREE_SPACE (tsp, (width/8)+2);                       \
        ptr = (IMUChar *) im_memory_chunk_get_ptr (tsp->_chunk);    \
        ptr += tsp->_write_pos;                                     \
        UINT16_TO_BYTES(ptr, type);                                 \
        UINT##width##_TO_BYTES(ptr+2, value);                       \
        tsp->_write_pos += ((width/8)+2);                           \
        return TRUE;                                                \
    } while (0)

static IMBool
__im_byte_stream_put_char (IMStream *stream, IMChar v_char)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_CHAR,8,v_char);
}
static IMBool
__im_byte_stream_put_uchar (IMStream *stream, IMUChar v_uchar)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_UCHAR,8,v_uchar);
}
static IMBool
__im_byte_stream_put_bool (IMStream *stream, IMBool v_bool)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_BOOL,8,v_bool);
}
static IMBool
__im_byte_stream_put_int16 (IMStream *stream, IMInt16 v_int16)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_INT16,16,v_int16);
}
static IMBool
__im_byte_stream_put_uint16 (IMStream *stream, IMUInt16 v_uint16)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_UINT16,16,v_uint16);
}
static IMBool
__im_byte_stream_put_int32 (IMStream *stream, IMInt32 v_int32)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_INT32,32,v_int32);
}
static IMBool
__im_byte_stream_put_uint32 (IMStream *stream, IMUInt32 v_uint32)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_UINT32,32,v_uint32);
}
static IMBool
__im_byte_stream_put_int64 (IMStream *stream, IMInt64 v_int64)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_INT64,64,v_int64);
}
static IMBool
__im_byte_stream_put_uint64 (IMStream *stream, IMUInt64 v_uint64)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_UINT64,64,v_uint64);
}
static IMBool
__im_byte_stream_put_double (IMStream *stream, IMDouble v_double)
{
    PUT_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_DOUBLE,64,(*((IMUInt64*)(&v_double))));
}

#define PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,type,width,array,num)   \
    do  {                                                               \
        IMByteStream *tsp = IM_BYTE_STREAM (stream);                    \
        IMUChar      *ptr;                                              \
        IMSize        i;                                                \
        _im_assert (tsp);                                               \
        ENSURE_FREE_SPACE (tsp, ((width/8)*num)+6);                     \
        ptr = (IMUChar *) im_memory_chunk_get_ptr (tsp->_chunk);        \
        ptr += tsp->_write_pos;                                         \
        UINT16_TO_BYTES(ptr, type);                                     \
        ptr += 2;                                                       \
        UINT32_TO_BYTES(ptr, num);                                      \
        ptr += 4;                                                       \
        if (num) {                                                      \
            if (width == 8 && sizeof((array)[0]) == 1) {                \
                memcpy (ptr, (array), num);                             \
            } else {                                                    \
                for (i = 0; i < num; ++i) {                             \
                    UINT##width##_TO_BYTES(ptr, (array)[i]);            \
                    ptr += (width/8);                                   \
                }                                                       \
            }                                                           \
        }                                                               \
        tsp->_write_pos += (((width/8)*num)+6);                         \
        return TRUE;                                                    \
    } while (0)

static IMBool
__im_byte_stream_put_c_string (IMStream *stream, const IMChar *v_string, IMSize v_length)
{
    if (v_length == (IMSize)(-1)) v_length = strlen (v_string);
    PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_C_STRING,8,v_string,v_length);
}
static IMBool
__im_byte_stream_put_byte_array (IMStream *stream, const IMPointer v_array, IMSize v_size)
{
    PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_BYTE_ARRAY,8,(IMUChar*)v_array,v_size);
}
static IMBool
__im_byte_stream_put_bool_array (IMStream *stream, const IMBool *v_array, IMSize v_size)
{
    PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_BOOL_ARRAY,8,v_array,v_size);
}
static IMBool
__im_byte_stream_put_int16_array (IMStream *stream, const IMInt16 *v_array, IMSize v_size)
{
    PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_INT16_ARRAY,16,v_array,v_size);
}
static IMBool
__im_byte_stream_put_uint16_array (IMStream *stream, const IMUInt16 *v_array, IMSize v_size)
{
    PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_UINT16_ARRAY,16,v_array,v_size);
}
static IMBool
__im_byte_stream_put_int32_array (IMStream *stream, const IMInt32 *v_array, IMSize v_size)
{
    PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_INT32_ARRAY,32,v_array,v_size);
}
static IMBool
__im_byte_stream_put_uint32_array (IMStream *stream, const IMUInt32 *v_array, IMSize v_size)
{
    PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_UINT32_ARRAY,32,v_array,v_size);
}
static IMBool
__im_byte_stream_put_int64_array (IMStream *stream, const IMInt64 *v_array, IMSize v_size)
{
    PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_INT64_ARRAY,64,v_array,v_size);
}
static IMBool
__im_byte_stream_put_uint64_array (IMStream *stream, const IMUInt64 *v_array, IMSize v_size)
{
    PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_UINT64_ARRAY,64,v_array,v_size);
}
static IMBool
__im_byte_stream_put_double_array (IMStream *stream, const IMDouble *v_array, IMSize v_size)
{
    PUT_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_DOUBLE_ARRAY,64,((IMUInt64*)v_array),v_size);
}
static IMBool
__im_byte_stream_put_object (IMStream *stream, IMConstPointer v_object)
{
    IMByteStream    *tsp = IM_BYTE_STREAM (stream);
    const IMObject  *obj = IM_CONST_OBJECT (v_object);
    const IMChar    *name;
    IMSize           len;
    IMUChar         *ptr;
    IMType           type;
    _im_assert (tsp && obj);

    type = im_object_get_type (obj);
    name = im_type_get_name (type);
    len  = strlen (name);
    if (len > 255) len = 255;

    if (type >= IM_TYPE_FIRST_USER_DEFINED)
        ENSURE_FREE_SPACE (tsp, 3+len);
    else
        ENSURE_FREE_SPACE (tsp, 2);

    ptr = (IMUChar *) im_memory_chunk_get_ptr (tsp->_chunk);
    ptr += tsp->_write_pos;
    UINT16_TO_BYTES(ptr, type);
    tsp->_write_pos += 2;
    ptr += 2;

    /* If it's a user defined type, the type name will also
     * be stored. */
    if (type >= IM_TYPE_FIRST_USER_DEFINED) {
        *ptr = (IMUChar) len;
        memcpy (ptr+1, name, len);
        tsp->_write_pos += (1 + len);
    }

    return im_object_serialize (obj, stream);
}
static void
__im_byte_stream_clear (IMStream *stream)
{
    IMByteStream *tsp = IM_BYTE_STREAM (stream);
    _im_assert (tsp);
    tsp->_write_pos = 0;
    tsp->_read_pos = 0;
    __clear_cur_info (tsp);
    /* Only clear the memory chunk if it's too large. */
    if (im_memory_chunk_get_size (tsp->_chunk) > MAX_ALLOC_BLOCK_SIZE)
        im_memory_chunk_clear (tsp->_chunk);
}

static IMType
__im_byte_stream_get_data_type (IMStream *stream)
{
    IMByteStream *tsp = IM_BYTE_STREAM (stream);
    const IMUChar *ptr;
    IMType type;
    _im_assert (tsp);

    if (tsp->_cur_type != IM_TYPE_INVALID)
        return tsp->_cur_type;

    if ((tsp->_read_pos+2) > tsp->_write_pos)
        return IM_TYPE_INVALID;

    ptr  = (const IMUChar *)im_memory_chunk_get_const_ptr (tsp->_chunk);
    ptr += tsp->_read_pos;

    type = BYTES_TO_UINT16(ptr);

    /* If the type is an user defined type, use the stored 
     * type name. */
    if (type >= IM_TYPE_FIRST_USER_DEFINED) {
        IMChar name[256];
        IMSize len;
        if ((tsp->_read_pos+3) >= tsp->_write_pos)
            return IM_TYPE_INVALID;
        len = *(ptr + 2);
        if ((tsp->_read_pos+3+len) >= tsp->_write_pos)
            return IM_TYPE_INVALID;
        memcpy (name, ptr + 3, len);
        name[len] = 0;
        type = im_type_from_name (name);
        tsp->_cur_type_len = len + 3;
    } else {
        tsp->_cur_type_len = 2;
    }

    tsp->_cur_type = type;
    return tsp->_cur_type;
}

static IMSize
__im_byte_stream_get_data_size (IMStream *stream)
{
    IMType        type = __im_byte_stream_get_data_type (stream);
    IMByteStream *tsp  = IM_BYTE_STREAM (stream);
    const IMUChar *ptr;
    _im_assert (tsp);

    if (type == IM_TYPE_INVALID || type == IM_TYPE_VOID)
        return 0;

    if (tsp->_cur_size)
        return tsp->_cur_size;

    /* Single element type */
    if (IM_TYPE_IS_OBJECT (type) || (type >= IM_TYPE_VOID && type <= IM_TYPE_DOUBLE)) {
        tsp->_cur_size = im_type_get_instance_size (type);
        return tsp->_cur_size;
    }

    /* It's an array type, so the size is stored in 4bytes right after type. */
    ptr  = (IMUChar *)im_memory_chunk_get_const_ptr (tsp->_chunk);
    ptr += tsp->_read_pos;
    ptr += tsp->_cur_type_len;

    if ((tsp->_read_pos + tsp->_cur_type_len + 4) > tsp->_write_pos)
        return 0;

    tsp->_cur_size = BYTES_TO_UINT32(ptr);
    tsp->_cur_size_len = 4;

    return tsp->_cur_size;
}
static IMBool
__im_byte_stream_get_void (IMStream *stream)
{
    IMType        type = __im_byte_stream_get_data_type (stream);
    IMByteStream *tsp  = IM_BYTE_STREAM (stream);
    _im_assert (tsp);

    if (type == IM_TYPE_VOID) {
        tsp->_read_pos += (tsp->_cur_type_len + tsp->_cur_size_len);
        __clear_cur_info (tsp);
        return TRUE;
    }
    return FALSE;
}

#define GET_FUNDAMENTAL_VALUE_TYPE(stream,dtype,cast_type,width,val)        \
    do {                                                                    \
        IMType        type = __im_byte_stream_get_data_type (stream);       \
        IMSize        size = __im_byte_stream_get_data_size (stream);       \
        IMByteStream *tsp  = IM_BYTE_STREAM (stream);                       \
        const IMUChar *ptr;                                                 \
        _im_assert (tsp);                                                   \
        /* Treat IM_TYPE_BOOL specially. */                                 \
        if (type == IM_TYPE_BOOL) size = 1;                                 \
        if (type == (dtype) && size == (width/8)) {                         \
            if (tsp->_read_pos + tsp->_cur_type_len +                       \
                tsp->_cur_size_len + size > tsp->_write_pos)                \
                return FALSE;                                               \
            ptr = (const IMUChar*)im_memory_chunk_get_const_ptr (tsp->_chunk);\
            ptr += tsp->_read_pos;                                          \
            ptr += tsp->_cur_type_len;                                      \
            ptr += tsp->_cur_size_len;                                      \
            if (val)                                                        \
                *(val) = (cast_type)BYTES_TO_UINT##width(ptr);              \
            tsp->_read_pos += (tsp->_cur_type_len + tsp->_cur_size_len);    \
            tsp->_read_pos += size;                                         \
            __clear_cur_info (tsp);                                         \
            return TRUE;                                                    \
        }                                                                   \
    } while (0)

static IMBool
__im_byte_stream_get_char (IMStream *stream, IMChar *v_char)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_CHAR,IMChar,8,v_char);
    return FALSE;
}
static IMBool
__im_byte_stream_get_uchar (IMStream *stream, IMUChar *v_uchar)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_UCHAR,IMUChar,8,v_uchar);
    return FALSE;
}
static IMBool
__im_byte_stream_get_bool (IMStream *stream, IMBool *v_bool)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_BOOL,IMBool,8,v_bool);
    return FALSE;
}
static IMBool
__im_byte_stream_get_int16 (IMStream *stream, IMInt16 *v_int16)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_INT16,IMInt16,16,v_int16);
    return FALSE;
}
static IMBool
__im_byte_stream_get_uint16 (IMStream *stream, IMUInt16 *v_uint16)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_UINT16,IMUInt16,16,v_uint16);
    return FALSE;
}
static IMBool
__im_byte_stream_get_int32 (IMStream *stream, IMInt32 *v_int32)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_INT32,IMInt32,32,v_int32);
    return FALSE;
}
static IMBool
__im_byte_stream_get_uint32 (IMStream *stream, IMUInt32 *v_uint32)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_UINT32,IMUInt32,32,v_uint32);
    return FALSE;
}
static IMBool
__im_byte_stream_get_int64 (IMStream *stream, IMInt64 *v_int64)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_INT64,IMInt64,64,v_int64);
    return FALSE;
}
static IMBool
__im_byte_stream_get_uint64 (IMStream *stream, IMUInt64 *v_uint64)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_UINT64,IMUInt64,64,v_uint64);
    return FALSE;
}
static IMBool
__im_byte_stream_get_double (IMStream *stream, IMDouble *v_double)
{
    GET_FUNDAMENTAL_VALUE_TYPE(stream,IM_TYPE_DOUBLE,IMUInt64,64,(IMUInt64*)v_double);
    return FALSE;
}

#define GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,dtype,cast_type,width,array,nmemb)      \
    do {                                                                                \
        IMType        type = __im_byte_stream_get_data_type (stream);                   \
        IMSize        size = __im_byte_stream_get_data_size (stream);                   \
        IMSize        len;                                                              \
        IMByteStream *tsp  = IM_BYTE_STREAM (stream);                                   \
        const IMUChar *ptr;                                                             \
        IMSize i;                                                                       \
        _im_assert (tsp);                                                               \
                                                                                        \
        if (type == (dtype)) {                                                          \
            ptr = (const IMUChar*)im_memory_chunk_get_const_ptr (tsp->_chunk);          \
            ptr += (tsp->_read_pos + tsp->_cur_type_len + tsp->_cur_size_len);          \
            if (tsp->_read_pos + tsp->_cur_type_len + tsp->_cur_size_len +              \
                (size * (width/8)) > tsp->_write_pos)                                   \
                return FALSE;                                                           \
            len = IM_MIN(size, nmemb);                                                  \
            if ((array) && len) {                                                       \
                if (width == 8 && sizeof((array)[0]) == 1) {                            \
                    memcpy ((array), ptr, len);                                         \
                    if (type == IM_TYPE_C_STRING && len < nmemb)                        \
                        (array)[len] = 0;                                               \
                } else {                                                                \
                    for (i = 0; i < len; ++i) {                                         \
                        (array)[i] = (cast_type) BYTES_TO_UINT##width(ptr);             \
                        ptr += (width/8);                                               \
                    }                                                                   \
                }                                                                       \
            }                                                                           \
                                                                                        \
            tsp->_read_pos += (tsp->_cur_type_len + tsp->_cur_size_len);                \
            tsp->_read_pos += (size * (width/8));                                       \
            __clear_cur_info (tsp);                                                     \
            return TRUE;                                                                \
        }                                                                               \
    } while (0)

static IMBool
__im_byte_stream_get_c_string (IMStream *stream, IMChar *v_string, IMSize v_length)
{
    GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_C_STRING,IMChar,8,v_string,v_length);
    return FALSE;
}

static IMBool
__im_byte_stream_get_byte_array (IMStream *stream, IMPointer v_array, IMSize v_size)
{
    GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_BYTE_ARRAY,IMUChar,8,(IMUChar*)v_array,v_size);
    return FALSE;
}
static IMBool
__im_byte_stream_get_bool_array (IMStream *stream, IMBool *v_array, IMSize v_size)
{
    GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_BOOL_ARRAY,IMBool,8,v_array,v_size);
    return FALSE;
}
static IMBool
__im_byte_stream_get_int16_array (IMStream *stream, IMInt16 *v_array, IMSize v_size)
{
    GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_INT16_ARRAY,IMInt16,16,v_array,v_size);
    return FALSE;
}
static IMBool
__im_byte_stream_get_uint16_array (IMStream *stream, IMUInt16 *v_array, IMSize v_size)
{
    GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_UINT16_ARRAY,IMUInt16,16,v_array,v_size);
    return FALSE;
}
static IMBool
__im_byte_stream_get_int32_array (IMStream *stream, IMInt32 *v_array, IMSize v_size)
{
    GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_INT32_ARRAY,IMInt32,32,v_array,v_size);
    return FALSE;
}
static IMBool
__im_byte_stream_get_uint32_array (IMStream *stream, IMUInt32 *v_array, IMSize v_size)
{
    GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_UINT32_ARRAY,IMUInt32,32,v_array,v_size);
    return FALSE;
}
static IMBool
__im_byte_stream_get_int64_array (IMStream *stream, IMInt64 *v_array, IMSize v_size)
{
    GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_INT64_ARRAY,IMInt64,64,v_array,v_size);
    return FALSE;
}
static IMBool
__im_byte_stream_get_uint64_array (IMStream *stream, IMUInt64 *v_array, IMSize v_size)
{
    GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_UINT64_ARRAY,IMUInt64,64,v_array,v_size);
    return FALSE;
}
static IMBool
__im_byte_stream_get_double_array (IMStream *stream, IMDouble *v_array, IMSize v_size)
{
    GET_FUNDAMENTAL_VALUE_ARRAY_TYPE(stream,IM_TYPE_DOUBLE_ARRAY,IMUInt64,64,(IMUInt64*)v_array,v_size);
    return FALSE;
}
static IMBool
__im_byte_stream_get_object (IMStream *stream, IMPointer v_object)
{
    IMType        type = __im_byte_stream_get_data_type (stream);
    IMByteStream *tsp  = IM_BYTE_STREAM (stream);
    IMType        otype = im_object_get_type (v_object);
    IMSize        old_read_pos;
    _im_assert (tsp);

    if (type == otype && IM_TYPE_IS_OBJECT (type)) {
        old_read_pos = tsp->_read_pos;
        tsp->_read_pos += tsp->_cur_type_len;
        __clear_cur_info (tsp);
        if (!im_object_deserialize (v_object, stream)) {
            tsp->_read_pos = old_read_pos;
            __clear_cur_info (tsp);
            return FALSE;
        }
        __clear_cur_info (tsp);
        return TRUE;
    }
    return FALSE;
}
static IMBool
__im_byte_stream_skip_data (IMStream *stream)
{
    IMType        type = __im_byte_stream_get_data_type (stream);
    IMSize        size = __im_byte_stream_get_data_size (stream);
    IMSize        element_size;
    IMByteStream *tsp  = IM_BYTE_STREAM (stream);
    _im_assert (tsp);

    if (type >= IM_TYPE_VOID && type <= IM_TYPE_DOUBLE) {
        /* Treat IM_TYPE_BOOL specially. */
        if (type == IM_TYPE_BOOL) size = 1;
        tsp->_read_pos += (tsp->_cur_type_len + tsp->_cur_size_len);
        tsp->_read_pos += size;
        __clear_cur_info (tsp);
        return TRUE;
    } else if (type >= IM_TYPE_C_STRING && type <= IM_TYPE_DOUBLE_ARRAY) {
        switch (type) {
            case IM_TYPE_C_STRING:
            case IM_TYPE_BYTE_ARRAY:
            case IM_TYPE_BOOL_ARRAY:
                element_size = 1;
                break;
            case IM_TYPE_INT16_ARRAY:
            case IM_TYPE_UINT16_ARRAY:
                element_size = 2;
                break;
            case IM_TYPE_INT32_ARRAY:
            case IM_TYPE_UINT32_ARRAY:
                element_size = 4;
                break;
            case IM_TYPE_INT64_ARRAY:
            case IM_TYPE_UINT64_ARRAY:
            case IM_TYPE_DOUBLE_ARRAY:
                element_size = 8;
                break;
            default:
                element_size = 0;
        }
        tsp->_read_pos += (tsp->_cur_type_len + tsp->_cur_size_len);
        tsp->_read_pos += (size * element_size);
        __clear_cur_info (tsp);
        return TRUE;
    } else if (IM_TYPE_IS_OBJECT (type)) {
        IMPointer obj = im_object_new (type);
        IMBool ret;
        if (!obj) return FALSE;
        ret = __im_byte_stream_get_object(stream, obj);
        im_object_unref (obj);
        return ret;
    }
    return FALSE;
}
static IMBool
__im_byte_stream_rewind (IMStream *stream)
{
    IMByteStream *p = IM_BYTE_STREAM (stream);

    _im_assert (p);

    p->_read_pos = 0;
    __clear_cur_info (p);

    return TRUE;
}

static IMBool
__im_byte_stream_read (IMStream *stream, IMIOChannel *channel,
                       IMInt *timeout, IMIOError *error)
{
    IMChar buf [16];
    IMUInt32 magic;
    IMUInt32 version;
    IMUInt32 size;
    IMUInt32 checksum;
    IMSize   bytes;

    IMInt def_timeout = -1;
    IMByteStream *p = IM_BYTE_STREAM (stream);
    _im_assert (p);

    if (!timeout) timeout = &def_timeout;

    bytes = 16;
    if (!im_io_channel_read_with_timeout (channel, buf, bytes, &bytes,
                                          timeout, error) ||
        bytes != 16) {
        if (error && *error == IM_IO_ERROR_NONE)
            *error = IM_IO_ERROR_FAILED;
        return FALSE;
    }

    magic    = BYTES_TO_UINT32(buf);
    version  = BYTES_TO_UINT32(buf+4);
    size     = BYTES_TO_UINT32(buf+8);
    checksum = BYTES_TO_UINT32(buf+12);

    if (magic != BYTE_STREAM_MAGIC || version != BYTE_STREAM_PROTOCOL_VERSION) {
        return FALSE;
    }

    if (im_memory_chunk_load (p->_chunk, channel, size, timeout, error)) {
        if (im_memory_chunk_get_checksum (p->_chunk) == checksum) {
            p->_write_pos = im_memory_chunk_get_size (p->_chunk);
            p->_read_pos       = 0;
            __clear_cur_info (p);
            return TRUE;
        }
        im_memory_chunk_clear (p->_chunk);
    }

    return FALSE;
}

static IMBool
__im_byte_stream_write (IMStream *stream, IMIOChannel *channel,
                        IMInt *timeout, IMIOError *error)
{
    IMChar buf [16];
    IMSize bytes;
    IMInt def_timeout = -1;
    IMByteStream *p = IM_BYTE_STREAM (stream);
    _im_assert (p);

    __free_unused_space (p);
    if (!timeout) timeout = &def_timeout;

    bytes = 16;
    UINT32_TO_BYTES(buf, BYTE_STREAM_MAGIC);
    UINT32_TO_BYTES(buf+4, BYTE_STREAM_PROTOCOL_VERSION);
    UINT32_TO_BYTES(buf+8, p->_write_pos);
    UINT32_TO_BYTES(buf+12, im_memory_chunk_get_checksum (p->_chunk));

    if (!im_io_channel_write_with_timeout (channel, buf, bytes, &bytes,
                                           timeout, error) ||
        bytes != 16) {
        if (error && *error == IM_IO_ERROR_NONE)
            *error = IM_IO_ERROR_FAILED;
        return FALSE;
    }

    return im_memory_chunk_save (p->_chunk, channel, p->_write_pos,
                                 timeout, error);
}

static IMSize
__im_byte_stream_size (IMStream *stream)
{
    IMByteStream *p = IM_BYTE_STREAM (stream);
    _im_assert (p);
    return im_memory_chunk_get_size (p->_chunk);
}


static void
__im_byte_stream_initialize (IMPointer obj)
{
    IMByteStream *p = IM_BYTE_STREAM (obj);

    _im_assert (p);

    p->_chunk           = im_object_ref (im_memory_chunk_new (0));
    p->_read_pos        = 0;
    p->_write_pos       = 0;
    __clear_cur_info (p);
}

static void
__im_byte_stream_finalize (IMPointer obj)
{
    IMByteStream *p = IM_BYTE_STREAM (obj);

    _im_assert (p);

    im_object_unref (p->_chunk);
}

static void
__im_byte_stream_copy (IMPointer dest, IMConstPointer src)
{
    IMByteStream        *dp = IM_BYTE_STREAM (dest);
    const IMByteStream  *sp = IM_CONST_BYTE_STREAM (src);

    _im_assert (dp && sp);

    im_object_unref (dp->_chunk);
    dp->_chunk          = im_object_ref (im_object_clone (sp->_chunk));
    dp->_read_pos       = sp->_read_pos;
    dp->_write_pos      = sp->_write_pos;
    dp->_cur_type       = sp->_cur_type;
    dp->_cur_size       = sp->_cur_size;
    dp->_cur_type_len   = sp->_cur_type_len;
    dp->_cur_size_len   = sp->_cur_size_len;

    /* call parent copy */
    __im_byte_stream_parent_class->copy (dest, src);
}

static IMBool
__im_byte_stream_serialize (IMConstPointer obj, IMPointer stream)
{
    const IMByteStream  *op = IM_CONST_BYTE_STREAM (obj);
    IMStream            *sp = IM_STREAM (stream);

    _im_assert (op && sp);

    __free_unused_space (op);

    /* Call parent serialize method */
    return __im_byte_stream_parent_class->serialize (obj, stream) &&
           im_stream_put_object (sp, op->_chunk);
}

static IMBool
__im_byte_stream_deserialize (IMPointer obj, IMPointer stream)
{
    IMByteStream *op = IM_BYTE_STREAM (obj);
    IMStream     *sp = IM_STREAM (stream);

    _im_assert (op && sp);

    /* Call parent serialize method */
    if (!__im_byte_stream_parent_class->deserialize (obj, stream))
        return FALSE;

    __im_byte_stream_clear ((IMStream*)op);

    if (im_stream_get_data_type (sp) != IM_TYPE_MEMORY_CHUNK)
        return FALSE;

    if (im_stream_get_object (sp, op->_chunk)) {
        op->_write_pos = im_memory_chunk_get_size (op->_chunk);
        return TRUE;
    }
    return FALSE;
}

static void
__im_byte_stream_class_init (IMPointer klass)
{
    IMObjectClass *cp  = (IMObjectClass *)klass;
    IMStreamClass *scp = (IMStreamClass *)klass;

    cp->copy                = __im_byte_stream_copy;
    cp->serialize           = __im_byte_stream_serialize;
    cp->deserialize         = __im_byte_stream_deserialize;

    scp->put_void           = __im_byte_stream_put_void;
    scp->put_char           = __im_byte_stream_put_char;
    scp->put_uchar          = __im_byte_stream_put_uchar;
    scp->put_bool           = __im_byte_stream_put_bool;
    scp->put_int16          = __im_byte_stream_put_int16;
    scp->put_uint16         = __im_byte_stream_put_uint16;
    scp->put_int32          = __im_byte_stream_put_int32;
    scp->put_uint32         = __im_byte_stream_put_uint32;
    scp->put_int64          = __im_byte_stream_put_int64;
    scp->put_uint64         = __im_byte_stream_put_uint64;
    scp->put_double         = __im_byte_stream_put_double;
    scp->put_c_string       = __im_byte_stream_put_c_string;
    scp->put_byte_array     = __im_byte_stream_put_byte_array;
    scp->put_bool_array     = __im_byte_stream_put_bool_array;
    scp->put_int16_array    = __im_byte_stream_put_int16_array;
    scp->put_uint16_array   = __im_byte_stream_put_uint16_array;
    scp->put_int32_array    = __im_byte_stream_put_int32_array;
    scp->put_uint32_array   = __im_byte_stream_put_uint32_array;
    scp->put_int64_array    = __im_byte_stream_put_int64_array;
    scp->put_uint64_array   = __im_byte_stream_put_uint64_array;
    scp->put_double_array   = __im_byte_stream_put_double_array;
    scp->put_object         = __im_byte_stream_put_object;
    scp->clear              = __im_byte_stream_clear;
    scp->get_data_type      = __im_byte_stream_get_data_type;
    scp->get_data_size      = __im_byte_stream_get_data_size;
    scp->get_void           = __im_byte_stream_get_void;
    scp->get_char           = __im_byte_stream_get_char;
    scp->get_uchar          = __im_byte_stream_get_uchar;
    scp->get_bool           = __im_byte_stream_get_bool;
    scp->get_int16          = __im_byte_stream_get_int16;
    scp->get_uint16         = __im_byte_stream_get_uint16;
    scp->get_int32          = __im_byte_stream_get_int32;
    scp->get_uint32         = __im_byte_stream_get_uint32;
    scp->get_int64          = __im_byte_stream_get_int64;
    scp->get_uint64         = __im_byte_stream_get_uint64;
    scp->get_double         = __im_byte_stream_get_double;
    scp->get_c_string       = __im_byte_stream_get_c_string;
    scp->get_byte_array     = __im_byte_stream_get_byte_array;
    scp->get_bool_array     = __im_byte_stream_get_bool_array;
    scp->get_int16_array    = __im_byte_stream_get_int16_array;
    scp->get_uint16_array   = __im_byte_stream_get_uint16_array;
    scp->get_int32_array    = __im_byte_stream_get_int32_array;
    scp->get_uint32_array   = __im_byte_stream_get_uint32_array;
    scp->get_int64_array    = __im_byte_stream_get_int64_array;
    scp->get_uint64_array   = __im_byte_stream_get_uint64_array;
    scp->get_double_array   = __im_byte_stream_get_double_array;
    scp->get_object         = __im_byte_stream_get_object;
    scp->skip_data          = __im_byte_stream_skip_data;
    scp->rewind             = __im_byte_stream_rewind;
    scp->read               = __im_byte_stream_read;
    scp->write              = __im_byte_stream_write;
    scp->size               = __im_byte_stream_size;

    __im_byte_stream_parent_class = im_object_class_get_parent (cp);
}

void
__im_type_register_byte_stream ()
{
    static IMTypeInfo im_byte_stream_type_info = 
    {
        sizeof (IMByteStreamClass), /**< class_size >**/
        __im_byte_stream_class_init,/**< class_init >**/
        0,                          /**< class_finalize >**/

        sizeof (IMByteStream),      /**< instance_size >**/
        __im_byte_stream_initialize,/**< instance_init >**/
        __im_byte_stream_finalize,  /**< instance_finalize >**/
    };

    __im_type_register_builtin_class (IM_TYPE_STREAM,
                                      IM_TYPE_BYTE_STREAM,
                                      "ByteStream",
                                      &im_byte_stream_type_info,
                                      TRUE);
}

IMStream*
im_byte_stream_new ()
{
    IMStream *s = (IMStream *) im_object_new (IM_TYPE_BYTE_STREAM);
    _im_assert (s);
    return s;
}

void
im_byte_stream_set_memory_chunk (IMByteStream *stream, IMMemoryChunk *chunk)
{
    _im_return_if_fail (IM_IS_BYTE_STREAM (stream));
    _im_return_if_fail (IM_IS_MEMORY_CHUNK (chunk));

    im_object_unref (stream->_chunk);
    stream->_chunk = chunk;
    im_object_ref (stream->_chunk);

    stream->_write_pos      = im_memory_chunk_get_size (stream->_chunk);
    stream->_read_pos       = 0;
    __clear_cur_info (stream);
}

IMMemoryChunk*
im_byte_stream_get_memory_chunk (const IMByteStream *stream)
{
    _im_return_val_if_fail (IM_IS_BYTE_STREAM (stream), 0);

    __free_unused_space (stream);

    return stream->_chunk;
}

IMMemoryChunk*
im_byte_stream_dup_memory_chunk (const IMByteStream *stream)
{
    _im_return_val_if_fail (IM_IS_BYTE_STREAM (stream), 0);

    return im_memory_chunk_get_sub_chunk (stream->_chunk, 0, stream->_write_pos, FALSE);
}

/*
vi:ts=4:nowrap:ai:expandtab
*/
